package com.yixing.tech.wms.service.impl;

import com.yixing.tech.common.base.BusinessException;
import com.yixing.tech.common.enums.YesOrNo;
import com.yixing.tech.common.service.AbstractWmsService;
import com.yixing.tech.wms.entity.LineCallEmptyWork;
import com.yixing.tech.wms.entity.ManualStockWork;
import com.yixing.tech.wms.service.LineOperationService;
import com.yixing.tech.wms.service.StockService;
import com.yixing.tech.wms.service.WmsTaskService;
import com.yixing.tech.wms.stock.out.StockOutConstants;
import com.yixing.tech.wms.stock.out.callback.error.ErrorCodeEnum;
import com.yixing.tech.wms.stock.out.callback.error.TaskErrorCallbackVO;
import com.yixing.tech.wms.stock.out.domain.Task;
import com.yixing.tech.wms.stock.out.domain.vo.TaskMethod;
import com.yixing.tech.wms.stock.out.domain.vo.TaskType;
import com.yixing.tech.wms.utils.DocumentUtils;
import com.yixing.tech.common.vo.s3.UserVO;
import com.mongodb.client.result.UpdateResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.bson.Document;
import org.bson.types.ObjectId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationOperation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.aggregation.ConvertOperators;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.*;

import static com.yixing.tech.common.CommonConstant.*;

/**
 * @author yixing tech
 * @since 1.0.0
 */
@Slf4j
@Service
public class LineOperationServiceImpl extends AbstractWmsService implements LineOperationService {

    @Autowired
    private StockService stockService;
    @Autowired
    private WmsTaskService wmsTaskService;
    @Value("${tenant-id: 6204cb763da841718ddd1cf8}")
    private String tenantId;

    @Override
    public List<Document> productLocations() throws BusinessException {
        // 查询配置
        Document set = getSet();

        String prodArea = set.getString("prodArea");
        if(StringUtils.isBlank(prodArea)) {
            return Collections.emptyList();
        }
       return simpleFind("entity_" + STOCK_LOCATION, "area", prodArea);
    }

    @Override
    public List<Document> rawLocations() throws BusinessException {
        // 查询配置
        Document set = getSet();
        String area = set.getString("rawArea");
        if(StringUtils.isBlank(area)) {
            return Collections.emptyList();
        }
        return simpleFind("entity_" + STOCK_LOCATION, "area", area);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean productCallEmpty(String materialCode, String endLocationCode) throws BusinessException {
        Document set = getSet();
        String prodArea = set.getString("prodArea");
        if(StringUtils.isBlank(prodArea)) {
            throw new BusinessException("未设置产线成品摆放区");
        }
        getLocation(endLocationCode);

        // 查询物料规则
       Document rule = simpleFindOne("entity_" + STOCK_RULE, "materialCode", materialCode);
        if (rule == null) {
            throw new BusinessException("物料规则不存在");
        }
        String containerType = Optional.ofNullable(rule.get("containerType", Document.class)).map(c -> c.getString("id")).orElse(null);

        List<String> areas = rule.getList("areas", String.class);
        if(CollectionUtils.isEmpty(areas)) {
            throw new BusinessException(String.format("物料[%]存储规则为配置存储区域", materialCode));
        }

        // 查询空容器
        List<Document> emptyContainers = stockService.queryEmptyContainers(containerType, areas);
        if(CollectionUtils.isEmpty(emptyContainers)) {
            throw new BusinessException("无空闲的容器");
        }
        Document emptyContainer = emptyContainers.get(0);
        String startLocationCode = emptyContainer.getString("locationCode");

        // 锁定库位
        Update update = new Update();
        update.set("lock", true);

        if (!versionUpdate(Query.query(newCriteria().and("code").is(startLocationCode)), update, "entity_" + STOCK_LOCATION)) {
            // 更新失败，需要重新申请
            throw new BusinessException("库位上锁失败，请重新下发");
        }

        String containerCode = emptyContainer.getString("containerCode");
        // 创建手动出库任务
        LineCallEmptyWork work = new LineCallEmptyWork();
        work.setCode(new ObjectId().toString());
        work.setContainerCode(containerCode);
        work.setStartArea(emptyContainer.getString("areaCode"));
        work.setStartLocation(startLocationCode);
        work.setEndArea(prodArea);
        work.setEndLocation(endLocationCode);
        work.setTenantId(tenantId);
        mongoTemplate.insert(work, "entity_" + LINE_OPERATION_CALL_EMPTY_WORK);

        // 锁定存储库位与容器
        if (!versionUpdate(Query.query(newCriteria().and("code").is(containerCode)), update, "entity_" + CONTAINER)) {
            // 更新失败，需要重新申请
            throw new BusinessException("容器上锁失败，请重新下发");
        }
        if (!versionUpdate(Query.query(newCriteria().and("code").is(endLocationCode)), update, "entity_" + STOCK_LOCATION)) {
            // 更新失败，需要重新申请
            throw new BusinessException("库位上锁失败，请重新下发");
        }

        // 创建下架任务
        Task task = Task.builder()
                .taskCode(UUID.randomUUID().toString().replaceAll("-", ""))
                .taskType(TaskType.STOCK_OUT.getId())
                .startLoc(startLocationCode)
                .endArea(prodArea)
                .endLoc(endLocationCode)
                .podCode(containerCode)
                .business(PRODUCT_CALL_EMPTY_BIZ)
                .operationNo(Collections.singletonList(work.getCode()))
                .build();
        wmsTaskService.saveTaskInfo(task);

        return true;
    }

    @Override
    public void productCallEmptyCallback(String taskCode, String method, String locCode, String podCode) {
        log.info("处理任务回调信息:taskCode[{}],method[{}],locCode[{}],podCode[{}]", taskCode, method, locCode, podCode);

        List<AggregationOperation> operations = new ArrayList<>();
        operations.add(Aggregation.match(Criteria.where("tenantId").is(tenantId).and("isDelete").ne(YesOrNo.YES.ordinal())
                .and("taskCode").is(taskCode)));
        operations.add(Aggregation.addFields()
                .addFieldWithValue("taskType", ConvertOperators.ToString.toString("$taskType.id"))
                .addFieldWithValue("status", ConvertOperators.ToString.toString("$status.id"))
                .addFieldWithValue("performStatus", ConvertOperators.ToString.toString("$performStatus.id"))
                .build());

        AggregationResults<Task> results = mongoTemplate.aggregate(Aggregation.newAggregation(operations), StockOutConstants.TASK_TEMPLATE, Task.class);
        if (ObjectUtils.isEmpty(results.getMappedResults())) {
            return;
        }
        Task task = results.getMappedResults().get(0);
        if (TaskMethod.OUTBIN.getAction().equals(method)) {
            // 处理离开起点
            handleOutBin(taskCode, method, locCode, podCode, task);
        } else if (TaskType.STOCK_OUT.getId().equals(task.getTaskType()) && TaskMethod.FINISH.getAction().equals(method)) {
            // 容器到达产线区域
            containerArrive(taskCode, method, locCode, podCode, task);
        }
    }

    @Override
    public Object productCallEmptyErrorCallback(TaskErrorCallbackVO errorCallbackVO) throws BusinessException {
        ErrorCodeEnum errorCodeEnum = ErrorCodeEnum.getByCode(errorCallbackVO.getErrorCode());
        if(ObjectUtils.isEmpty(errorCodeEnum)){
            log.error("异常码不在预期范围内:errorCode[{}]", errorCallbackVO.getErrorCode());
            throw new BusinessException("异常码不在预期范围内");
        }

        Task task = wmsTaskService.findTaskByTaskCode(errorCallbackVO.getTaskCode());
        if(ObjectUtils.isEmpty(task)) {
            log.error("异常任务信息不存在:taskCode[{}]", errorCallbackVO.getTaskCode());
            throw new BusinessException("异常任务信息不存在");
        }

        Object result = handleCommonError(task);

//        switch (errorCodeEnum) {
//            case DOUBLE_IN:
//                result = doubleIn(task);
//                break;
//            case DOUBLE_IN2:
//                result = doubleIn2(task);
//                break;
//            case FETCHING_NOTHING:
//                result = fetchingNothing(task);
//                break;
//            case FETCHING_NOTHING2:
//                result = fetchingNothing2(task);
//        }
        return result;
    }

    @Transactional(rollbackFor = Exception.class)
    public String handleCommonError(Task task) throws BusinessException {
        List<String> workCodes = task.getOperationNo();
        LineCallEmptyWork work = mongoTemplate.findOne(Query.query(newCriteria().and("code").in(workCodes)),
                LineCallEmptyWork.class, "entity_" + LINE_OPERATION_CALL_EMPTY_WORK);
        // 下架任务取消
        return cancelWork(work);
    }

    @Transactional(rollbackFor = Exception.class)
    public String cancelWork(LineCallEmptyWork work) {
        Update delete = new Update();
        delete.set("isDelete", 1);
        updateById(work.get_id().toString(), delete, "entity_" + LINE_OPERATION_CALL_EMPTY_WORK);
        return "";
    }

    @Transactional(rollbackFor = Exception.class)
    public void handleOutBin(String taskCode, String method, String locCode, String podCode, Task task) {
        List<String> operationNo = task.getOperationNo();
        if (!CollectionUtils.isEmpty(operationNo)) {
            Update update = new Update();
            update.set("status", ManualStockWork.ManualStockWorkStatus.running.name());
            update.set("updateTime", new Date());
            mongoTemplate.updateMulti((Query.query(newCriteria().and("code").in(operationNo))), update, "entity_" + LINE_OPERATION_CALL_EMPTY_WORK);
        }

        // 处理离开起点
        // 起点库位解绑容器，并解锁
        Update update = new Update();
        update.unset("containerCode");
        update.set("lock", false);
        UpdateResult updateResult = mongoTemplate.updateFirst(Query.query(newCriteria().and("code").is(locCode)), update,
                "entity_" + STOCK_LOCATION);
        log.info("任务离开起点节点回调,更新库位[{}]绑定的容器[{}],更新结果[{}]", locCode, update, updateResult);
    }

    @Transactional(rollbackFor = Exception.class)
    public void containerArrive(String taskCode, String method, String locCode, String podCode, Task task) {
        List<String> operationNo = task.getOperationNo();
        if (!CollectionUtils.isEmpty(operationNo)) {
            Update update = new Update();
            update.set("status", ManualStockWork.ManualStockWorkStatus.finished.name());
            update.set("endLocation", locCode);
            update.set("updateTime", new Date());
            mongoTemplate.updateMulti((Query.query(newCriteria().and("code").in(operationNo))), update, "entity_" + LINE_OPERATION_CALL_EMPTY_WORK);
        }

        // 解锁容器
        unlockContainer(podCode);
        // 容器到达
        Update update = new Update();
        update.set("containerCode", podCode);
        update.set("lock", false);
        UpdateResult updateResult = mongoTemplate.updateFirst(Query.query(newCriteria().and("code").is(locCode)),
                update, "entity_" + STOCK_LOCATION);

        log.info("任务离开起点到达产线区,更新库位[{}]绑定的容器[{}],更新结果[{}]", locCode, update, updateResult);
    }

    private void unlockContainer(String containerCode) {
        Update update2 = new Update();
        update2.set("lock", false);
        update2.set("updateTime", new Date());
        mongoTemplate.updateFirst((Query.query(newCriteria().and("code").is(containerCode))), update2, "entity_" + CONTAINER);
    }

    private Document getLocation(String code) throws BusinessException {
        Document location = simpleFindOne("entity_" + STOCK_LOCATION, "code", code);
        if (location == null) {
            throw new BusinessException(String.format("库位[%s]不存在", code));
        }
        if (location.getBoolean("lock")) {
            throw new BusinessException(String.format("库位[%s]已被锁定", code));
        }
        return location;
    }

    private Document getSet() throws BusinessException {
        UserVO apiUser = getApiUser();
        String userId = apiUser.getId();

        // 查询配置
        Document set = simpleFindOne("entity_" + LINE_OPERATION_SET, "users",
                DocumentUtils.newDocument("id", new ObjectId(userId)));
        if(set == null) {
            throw new BusinessException(String.format("用户[%s]的产线操作配置不存在", apiUser.getName()));
        }
        return set;
    }
}
